package com.teemor.tim.command;

import com.alibaba.fastjson.JSON;
import com.teemor.tim.command.model.BaseCommand;
import com.teemor.tim.command.model.CommandParam;
import com.teemor.tim.bean.result.callback.CallBackResult;
import com.teemor.tim.command.annation.CommandMapping;
import com.teemor.tim.utils.ContextWarer;
import lombok.Data;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
 * @ename zhoulk
 * @cname 周李科
 * @date 2020/12/9 15:51
 */
@Component
public class CommandExecute implements ApplicationRunner {

    private final Map<String, CommandAdaptor> handlerMap = new HashMap<>();

    public CallBackResult execute(CommandParam param, BaseCommand command, String content) {
        String commandKey = command.getCallbackCommand();
        CommandAdaptor adaptor = this.handlerMap.get(commandKey);
        CallBackResult result = new CallBackResult();
        if (adaptor != null) {
            command  = JSON.parseObject(content, adaptor.commandType);
            command.setCommandParam(param);
            result = (CallBackResult) adaptor.invoke(command);
        }
        return result;
    }

    public CallBackResult execute(BaseCommand command) {
        CommandAdaptor adaptor = this.handlerMap.get(command.getCallbackCommand());
        CallBackResult result = new CallBackResult();
        if (adaptor != null) {
            result = (CallBackResult) adaptor.invoke(command);

        }
        return result;
    }

    private static CommandExecute commandExecute;

    public static CommandExecute instance() {
        if (commandExecute == null) {
            commandExecute = ContextWarer.getBean(CommandExecute.class);
        }
        return commandExecute;
    }

    @Override
    public void run(ApplicationArguments args) {
        String[] beanNames = ContextWarer.getApplicationContext().getBeanNamesForAnnotation(CommandMapping.class);
        for (String beanName : beanNames) {
            Object bean = ContextWarer.getBean(beanName);
            Class<?> clazz = bean.getClass();
            CommandMapping clazzHandler = clazz.getAnnotation(CommandMapping.class);
            String pre = clazzHandler.value();

            Method[] methods = clazz.getDeclaredMethods();
            for (Method method : methods) {
                if (method.isAnnotationPresent(CommandMapping.class)) {
                    CommandMapping handler = method.getDeclaredAnnotation(CommandMapping.class);
                    String handlerName = handler.value();
                    String command = pre + "." + handlerName;
                    Class<? extends BaseCommand> commandType = BaseCommand.class;
                    if (method.getParameterTypes().length > 0 && method.getParameterTypes()[0].getSuperclass().equals(BaseCommand.class)) {
                        commandType = (Class<? extends BaseCommand>) method.getParameterTypes()[0];
                    }
                    Class<?> returnType = method.getReturnType();
                    CommandAdaptor adaptor = new CommandAdaptor(handlerName, bean, method, commandType, returnType);
                    handlerMap.put(command, adaptor);
                }
            }

        }
    }

    @Data
    private static class CommandAdaptor {
        private String commandName;

        private Object handlerBean;

        private Method method;

        private Class<? extends BaseCommand> commandType;

        private Class<?> returnType;

        public CommandAdaptor(String commandName, Object handlerBean, Method method, Class<? extends BaseCommand> commandType, Class<?> returnType) {
            this.commandName = commandName;
            this.handlerBean = handlerBean;
            this.method = method;
            this.commandType = commandType;
            this.returnType = returnType;
        }

        public Object invoke(BaseCommand command) {
            Object result = null;
            try {
                result = method.invoke(handlerBean, command);
            } catch (IllegalAccessException | InvocationTargetException e) {
                e.printStackTrace();
            }
            return result;
        }
    }
}
